from abc import ABC, abstractmethod
from typing import List, Dict, Any, Tuple
import logging
import re
from config.app_config import AppConfig

logger = logging.getLogger(__name__)


class BaseTextSplitter(ABC):
    """文本分块器基类"""

    @abstractmethod
    def split_text(self, text: str) -> List[str]:
        pass

    @abstractmethod
    def split_documents(self, documents: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        pass


class DefaultTextSplitter(BaseTextSplitter):
    """优化后的文本分块器（增强语义边界保护）"""

    def __init__(self, config: AppConfig = None):
        self.config = config or AppConfig()
        # 1. 分层分隔符（优先级：语义完整性 > 标点 > 空格）
        self.separators = [
            # 段落级（最高优先级）
            ("\n\n", "paragraph_break"),  # 空行（段落结束）
            ("\n", "line_break"),  # 单行分隔
            # 语义单元级（保护完整逻辑）
            ('。"', 'quoted_sentence_end'),  # 引号内句子结束
            ('。’', 'single_quoted_end'),  # 单引号内结束
            ('。）', 'parenthetical_end'),  # 括号内结束
            # 句子级（中文核心标点）
            ("。", "period"),
            ("！", "exclamation"),
            ("？", "question"),
            # 句子级（英文）
            (". ", "english_period"),
            ("! ", "english_exclamation"),
            ("? ", "english_question"),
            # 短语级（避免切断意群）
            ("，", "comma"),
            ("；", "semicolon"),
            ("、", "enumeration"),
            # 兜底分隔符
            (" ", "space"),
        ]

        # 2. 关键参数（可通过配置调整）
        self.chunk_size = self.config.chunk_size
        self.chunk_overlap = self.config.chunk_overlap
        self.min_chunk_length = max(100, int(self.chunk_size * 0.2))  # 最小片段长度（放宽至20%）
        self.max_iterations = 1000

        # 3. 语义边界正则（识别不可分割的单元）
        self.semantic_patterns = [
            re.compile(r'“.*?”'),  # 双引号内容（如“这是完整引用”）
            re.compile(r'‘.*?’'),  # 单引号内容
            re.compile(r'（.*?）'),  # 括号内容（如（注释说明））
            re.compile(r'\[.*?\]'),  # 方括号内容（如[标记]）
            re.compile(r'\d+\.\s'),  # 列表项（如1. 2. ）
        ]

    def _find_semantic_boundary(self, text: str, start: int, end: int) -> int:
        """查找最佳语义分割点，避免切断完整单元"""
        # 优先使用预设分隔符
        for sep, sep_type in self.separators:
            sep_pos = text.rfind(sep, start, end)
            if sep_pos != -1:
                # 检查分隔符是否在语义单元内（如引号内的句号不适合分割）
                if not self._is_inside_semantic_unit(text, sep_pos):
                    return sep_pos + len(sep)

        # 4. 兜底策略：在最近的语义单元边界分割
        return self._find_fallback_boundary(text, start, end)

    def _is_inside_semantic_unit(self, text: str, pos: int) -> bool:
        """判断位置是否在不可分割的语义单元内（如引号中）"""
        for pattern in self.semantic_patterns:
            for match in pattern.finditer(text):
                if match.start() < pos < match.end():
                    return True
        return False

    def _find_fallback_boundary(self, text: str, start: int, end: int) -> int:
        """当无理想分隔符时，找最近的语义安全点"""
        # 扩大搜索范围（最多向前找100字符）
        search_start = max(start, end - 100)
        # 优先在语义单元结束处分割
        for pattern in self.semantic_patterns:
            for match in pattern.finditer(text, search_start, end):
                return match.end()  # 在单元结束后分割
        # 最后尝试在词性边界分割（中文按字，英文按词）
        for i in range(end - 1, search_start, -1):
            if text[i].isspace() or (text[i].isascii() != text[i - 1].isascii()):
                return i
        return end  # 极端情况：强制截断

    def split_text(self, text: str) -> List[str]:
        chunks = []
        start = 0
        text_length = len(text)
        iteration = 0

        while start < text_length:
            iteration += 1
            if iteration > self.max_iterations:
                logger.warning(f"分块循环超限，强制终止（start={start}）")
                if start < text_length:
                    chunks.append(text[start:].strip())
                break

            # 计算目标结束位置（预留重叠空间）
            end = start + self.chunk_size
            if end >= text_length:
                chunk = text[start:].strip()
                if chunk:
                    chunks.append(chunk)
                break

            # 核心优化：找到语义安全的分割点
            best_split_pos = self._find_semantic_boundary(text, start, end)

            # 提取当前块并清理
            chunk = text[start:best_split_pos].strip()

            # 5. 处理过短/过长片段（保护语义完整性）
            if len(chunk) < self.min_chunk_length:
                # 合并到下一个块（避免过短碎片）
                start = best_split_pos
                continue
            if len(chunk) > self.chunk_size * 1.5:
                # 过长片段强制二次分割（避免超出预期）
                sub_chunks = self._split_overlong_chunk(chunk)
                chunks.extend(sub_chunks)
                start = best_split_pos
                continue

            if chunk:
                chunks.append(chunk)

            # 计算下一个起始位置（考虑重叠）
            next_start = best_split_pos - self.chunk_overlap
            next_start = max(start + 1, next_start)  # 确保前进
            start = next_start

        logger.info(f"分块完成：{len(chunks)}个片段（总长度：{text_length}）")
        return chunks

    def _split_overlong_chunk(self, chunk: str) -> List[str]:
        """处理超长片段（如无标点的长句）"""
        sub_chunks = []
        sub_start = 0
        sub_length = len(chunk)
        # 按1.2倍chunk_size分割（比正常略长，减少切割次数）
        split_size = int(self.chunk_size * 1.2)

        while sub_start < sub_length:
            sub_end = sub_start + split_size
            if sub_end >= sub_length:
                sub_chunks.append(chunk[sub_start:].strip())
                break
            # 在片段内部找安全分割点
            split_pos = self._find_semantic_boundary(chunk, sub_start, sub_end)
            sub_chunks.append(chunk[sub_start:split_pos].strip())
            sub_start = split_pos - self.chunk_overlap  # 保持重叠
        return [c for c in sub_chunks if c]

    def split_documents(self, documents: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        split_docs = []
        for doc in documents:
            text = doc.get("page_content", "")
            chunks = self.split_text(text)
            for i, chunk in enumerate(chunks):
                meta = doc.get("metadata", {}).copy()
                meta["chunk_index"] = i
                meta["chunk_total"] = len(chunks)
                split_docs.append({
                    "page_content": chunk,
                    "metadata": meta
                })
        return split_docs


class MarkdownTextSplitter(BaseTextSplitter):
    """增强版Markdown分块器（保护列表、代码块等结构）"""

    def __init__(self, config: AppConfig = None):
        self.config = config or AppConfig()
        self.default_splitter = DefaultTextSplitter(config)
        # 识别Markdown特殊结构
        self.special_blocks = [
            re.compile(r'```.*?```', re.DOTALL),  # 代码块
            re.compile(r'^\s*[-*+]\s+', re.MULTILINE),  # 无序列表
            re.compile(r'^\s*\d+\.\s+', re.MULTILINE),  # 有序列表
        ]

    def split_text(self, text: str) -> List[str]:
        # 1. 先提取特殊块（避免被分割）
        blocks = []
        last_end = 0
        # 标记所有特殊块位置
        special_matches = []
        for pattern in self.special_blocks:
            special_matches.extend(pattern.finditer(text))
        # 按位置排序
        special_matches.sort(key=lambda x: x.start())

        # 2. 分割普通文本和特殊块
        for match in special_matches:
            if match.start() > last_end:
                # 添加特殊块前的普通文本
                blocks.append({
                    "type": "normal",
                    "content": text[last_end:match.start()]
                })
            # 添加特殊块（完整保留）
            blocks.append({
                "type": "special",
                "content": match.group()
            })
            last_end = match.end()
        # 添加剩余文本
        if last_end < len(text):
            blocks.append({
                "type": "normal",
                "content": text[last_end:]
            })

        # 3. 处理普通文本，保留特殊块完整
        all_chunks = []
        for block in blocks:
            if block["type"] == "special":
                # 特殊块整体作为一个或多个片段（按长度分割）
                if len(block["content"]) > self.default_splitter.chunk_size * 1.5:
                    # 超长特殊块（如大段代码）需分割，但保持内部结构
                    sub_chunks = self.default_splitter.split_text(block["content"])
                    all_chunks.extend(sub_chunks)
                else:
                    all_chunks.append(block["content"])
            else:
                # 普通文本用默认分块器处理
                all_chunks.extend(self.default_splitter.split_text(block["content"]))

        return all_chunks

    def split_documents(self, documents: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        result = []
        for doc in documents:
            if doc.get("metadata", {}).get("file_type") == "markdown":
                split_docs = self.split_text(doc["page_content"])
                for i, split_doc in enumerate(split_docs):
                    meta = doc.get("metadata", {}).copy()
                    meta["chunk_index"] = i
                    result.append({
                        "page_content": split_doc,
                        "metadata": meta
                    })
            else:
                result.extend(self.default_splitter.split_documents([doc]))
        return result


class TextSplitterFactory:
    @staticmethod
    def get_splitter(document_type: str = "default", config: AppConfig = None) -> BaseTextSplitter:
        if document_type == "markdown":
            return MarkdownTextSplitter(config)
        # 可扩展：添加PDF/Word专用分块器（如识别表格、图片说明）
        return DefaultTextSplitter(config)
